wmctrl_buggy=false  -- set true until ubuntu 10.10, false since 11.04
isNatty=false  -- set true for ubuntu 11.04

script_path=string.sub(arg[0],1,-13)
config_path='/dev/shm' -- This is a ram-disk, so only for temporary settings. 
package.path=config_path.."/?.lua;"..package.path..";"..script_path.."/?.lua"
dofile(script_path..'/mylib.lua')
dofile(script_path..'/mstiler_config.lua')
pairsByKeys=pairs
function check_class(name1, name2) -- check if name1 ends with name2
	if name1 and type(name1)=='string' then 
		--string.lower(name1, string.sub(name1, string.len(name2)*-1))==string.lower(name2) then
		local idx=string.find(string.lower(name1), string.lower(name2))
		return idx~=nil
	end
	return false
end

--- User configuration started
exclude={'x-nautilus-desktop'} -- by title. (use this if possible)
exclude_id={} -- by window id
exclude_rule=function (win) -- by class or other properties
	--print(string.sub(win.class, -9))
	if (not win.resizable and not win.fullscreen and not win.maximized)
		or win.isDock
		or check_class(win.class, 'gnome_panel')
		or check_class(win.class, 'onboard')
		or check_class(win.class, 'conky')
		or check_class(win.class,'mstiler_.+%.py') -- means mstiler_*.py
		or check_class(win.class,'.+Screenlet%.py') -- means *Screenlet.py
		or check_class(win.class, 'zenity')
		or (win.state =="Iconic" and not win.shaded) 
		or win.monitor~=curr_monitor and not win.isActive then
		if debug then print(win.class, win.state, win.shaded, win.resizable, win.info, "excluded") end
		return true
	end
	return false
end

split_proportion=split_proportion or {0.6,0.6,0.6,0.6,0.6,0.6,0.6,0.6} -- non-uniform divide 
fullscreen_only_current_window=true
debug=false
SNAP=80
horiz_gap=0
vert_gap=3
adjust_step={v=0.08, h=0.08, t=0.25} -- do not change t=0.25 part
cycle_mode_table={v='h', h='f', f='l', t='v',l='v'} 
--cycle_mode_table={v='h', h='t', t='v', f='v',l='v'} -- you can skip unused mode like this.

--- User configuration finished. Do not modify below unless you know what you are doing.

-- all layout mode are implemented in the makeSlots function.
modes={h='Horizontal split mode', f='Fullscreen mode', v='Vertical split mode', t='Uniform tile mode',l='Uniform tile mode'}
notify_prefix=""


monitors={}
curr_monitor=curr_monitor or 1

function clamp(i,a,b)
	if a>b then a,b=b,a end
	return math.min(math.max(i,a),b)
end

function detect_resolution()
	local s,e,w,h,x,y,vx,vy,DG_w,DG_h -- detect monitors
	local xrandr=os.capture('xrandr',true)

	monitors={}
	while true do
		s,e,w,h,x,y=string.find(xrandr, 'connected (%d+)x(%d+)+(%d+)+(%d+)')
		if s==nil then 
			break 
		else
			if #monitors>=1 and
			tonumber(x)==monitors[#monitors].x and
			tonumber(y)==monitors[#monitors].y then
				-- skipping cloned monitor
			else
				monitors[#monitors+1]={x=tonumber(x), y=tonumber(y), w=tonumber(w), h=tonumber(h)}
			end
			xrandr=string.sub(xrandr, e+1)
		end
	end
	local fn=config_path.."/_monitors.lua"
	util.writeFile(fn, "return "..table.tostring(monitors))
	curr_monitor=clamp(curr_monitor, 1, #monitors)
end
do -- automatic detection of monitor resolution using xrandr

	local fn=config_path.."/_monitors.lua"
	local f=io.open(fn,'r')
	if not (arg[1]=='changeMode' and arg[2]=='v') and f~=nil then -- use cached resolution
		contents=f:read("*a")
		local func=loadstring(contents)
		monitors=func()
		f:close()
		curr_monitor=clamp(curr_monitor, 1, #monitors)
	else
		detect_resolution()
	end
end

function notify(msg)
	if msg then
		os.execute('python "'..script_path..'/mstiler_notify.py" "'..notify_prefix..msg..'"&')
		notify_prefix=""
	end
end

do
	 -- automatic detection of screen resolution using wmctrl
	local s,e,w,h,x,y,vx,vy,DG_w,DG_h
	local wmctrld=os.capture('wmctrl -d',true)
	s,e,x,y,w,h=string.find(wmctrld, 'WA: (%d+),(%d+) (%d+)x(%d+)')
	s,e,vx,vy=string.find(wmctrld, 'VP: (%d+),(%d+)')
	s,e,DG_w,DG_h=string.find(wmctrld, 'DG: (%d+)x(%d+)')
	assert(w)
	assert(h)
	x=tonumber(x)
	y=tonumber(y)
	w=tonumber(w)
	h=tonumber(h)
	screen_offset_x=x
	screen_offset_y=y
	screen_width=w
	screen_height=h 
	compizEnabled=tonumber(DG_w)>=w*2
	if compizEnabled then
		numWorkspaceCol=math.floor(tonumber(DG_w)/(screen_offset_x+screen_width)+0.5)
		numWorkspaceRow=math.floor(tonumber(DG_h)/(screen_offset_y+screen_height)+0.5)
		curr_workspace=math.floor(tonumber(vx)/(x+w)+0.5)+1+numWorkspaceCol* (math.floor(tonumber(vy)/(y+h)+0.5))
		if debug then print('vx,vy,numCol,numRow,currWsp=',vx,vy,numWorkspaceCol, numWorkspaceRow, curr_workspace) end
	else
		local c
		s,e,c=string.find(wmctrld, '(%d+)  %* DG:')
		curr_workspace=tonumber(c)+1
	end
	if debug then print(w,h) end
	--print(screen_offset_x, screen_offset_y)
	for i=1,#monitors do
		local m=monitors[i]
		if m.x+m.w> screen_offset_x+screen_width +10 or
		 m.y+m.h> screen_offset_y+screen_height +10 then
			 detect_resolution()
			 notify('monitor change detected')
		 end
	 end
end
function configIndex(curr_workspace)
	return curr_workspace*#monitors+curr_monitor -1
end

function setActiveWindow(id)
	os.execute('wmctrl -i -R '..id)
end

function focusWin(delta)
	local cmode,cproportion=getTilingMode()
	--if cmode=='h' then delta=delta*-1 end
	--local tbl=getWindowsInCurrWorkspace(true)
	local tbl=sortWindows(nil,_rawgetWindowsInCurrWorkspace()) -- speed optimized (reduced # of calls to xprop)
	local h=getActiveWindow()
	local cidx=1
	for i,w in ipairs(tbl) do if w.id==h then cidx=i break end end

	if cidx and #tbl>=1 then
		local idx=cidx
		while true do
			idx=math.mod(idx+delta-1+#tbl,#tbl)+1
			setProperty(tbl[idx])	
			if not exclude_rule(tbl[idx]) then break end
			if idx==cidx then break end
		end
		setActiveWindow(tbl[idx].id)
		local fullscreen=hasFullscreenWin(tbl)
		if fullscreen and idx then
			notify(string.format('%d/%d : %s', idx,#tbl, tbl[idx].info))
		end
	end
end
function saveConfig()
	local contents=[[
	-- This config file is automatically generated by mstiler.lua
	-- all arrays are index by workspace
	--
	]]

	for k,v in pairs(modes) do
		local prop=_G['tiling_proportion_'..k] or {0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5}
		contents=contents..'tiling_proportion_'..k..'='..table.tostring(prop).."\n\t"
	end
	contents=contents..[[tiling_mode=]]..table.tostring(tiling_mode).."\n"
	contents=contents..[[split_proportion=]]..table.tostring(split_proportion).."\n"
	contents=contents..[[curr_monitor=]]..curr_monitor.."\n"
	util.writeFile(script_path.."/mstiler_config.lua",contents)
end
-- dependencies: xprop, wmctrl, lua

-- output: { {id=.., x=.., y=.., workspace=..}}
-- workspace:0 means the current, -1 means left, 1 means right, and so on
function getWindows()
	local lines=string.lines(os.capture('wmctrl -lG', true))
	local windows={}
	for i=1, #lines do
		local parsed=array.filter(function(a) return a~="" end, string.tokenize(lines[i],"%s"))
		if parsed[2]~="-1" and parsed[2]~=nil then -- exclude non-user launched apps
			-- note that wmctrl -lG gives incorrect position. I reverse-engineered to identify correct position.
			local win
			if wmctrl_buggy then 
				win={id=tonumber(parsed[1]), x=tonumber(parsed[3])/2-1, y=tonumber(parsed[4])/2-29, w=tonumber(parsed[5]), h=tonumber(parsed[6]),info=table.concat(parsed," ", 8) }
			else
				win={id=tonumber(parsed[1]), x=tonumber(parsed[3]), y=tonumber(parsed[4]), w=tonumber(parsed[5]), h=tonumber(parsed[6]),info=table.concat(parsed," ", 8) }
			end

			--win.h=win.h+titlebar_height
			if compizEnabled then
				-- print(win.x, win.w, screen_offset_x+screen_width)
				win.wsp=getWsp(win)
			else
				win.wsp=tonumber(parsed[2])+1-curr_workspace
			end
			if debug then print(win.id,exclude_id[1], string.isOneOf(win.id, unpack(exclude_id))) end
			if not string.isMatched(win.info, exclude) and not string.isOneOf(win.id, unpack(exclude_id)) then
				windows[#windows+1]=win
			else
				if debug then print ('ignored', win.info) end
			end
		end
	end
	return windows
end

function getActiveWindow()
	local str=os.capture('xprop -root _NET_ACTIVE_WINDOW')
	local se=string.find(str, 'id # ')
	local adr=string.sub(str, se+5)
	local num=(tonumber(adr))
	if num==nil then
		local se2=string.find(adr, ',')
		num=tonumber(string.sub(adr, 1,se2-1))
	end
	return  num
end

function changeMode(nmode)

	local cmode,cproportion=getTilingMode()
	local tbl=getWindowsInCurrWorkspace(true)
	local win1=detectWin1(tbl, cmode)
	local tbl=sortWindows(win1.id, tbl)
	assert(nmode)
	--print('changeMode',nmode)
	tiling_mode[configIndex(curr_workspace)]=nmode
	cmode=nmode

	saveConfig()
	if nmode=='f' then
		local h=getActiveWindow()
		retileWindow(tbl)
		setActiveWindow(h)
	else
		retileWindow(tbl)
	end
	local msg=modes[cmode]

	notify(msg)
end
function hasFullscreenWin(tbl)
	local fullscreen=false
	for i=1, #tbl do
		if tbl[i].fullscreen then
			fullscreen=true
			break
		end
	end
	return fullscreen
end
function sortWindows(top_id, tbl, top_id_pos)
	if false then
		local sortedTbl={}
		for i=1, #tbl do
			if tbl[i].id==top_id then
				for j=i, #tbl do
					sortedTbl[#sortedTbl+1]=tbl[j]
				end
				for j=1,i-1 do
					sortedTbl[#sortedTbl+1]=tbl[j]
				end
				return sortedTbl
			end
		end

		return tbl
	else
		local sorted=shallowCopyTable(tbl)
		local sortRefTable={}
		local fn=config_path.."/_windoworder"..curr_workspace.."_"..curr_monitor..".lua"
		local f=io.open(fn,'r')
		if f~=nil then 
			contents=f:read("*a")
			local func=loadstring(contents)
			sortRefTable=func()
			f:close()
		end
		--printTable(sortRefTable)
		for i=1,#sorted do
			sorted[i].idx=i
		end
		if top_id then
			local top_id=tostring(top_id)
			if top_id_pos==nil then
				sortRefTable[top_id]=0
			else
				sortRefTable[top_id]=(sortRefTable[top_id] or 0)-top_id_pos
			end
		end
		--printTable(sortRefTable)

		table.sort(sorted, 
		function(a,b) 
			local aid=tostring(a.id)
			local bid=tostring(b.id)
			if sortRefTable[aid]==nil then
				if sortRefTable[bid]==nil then
					return a.idx<b.idx
				else
					return false
				end
			else
				if sortRefTable[bid]==nil then
					return true
				else
					return sortRefTable[aid]<sortRefTable[bid]
				end
			end
		end
		)
		-- save
		local sortRefTableOld=sortRefTable
		sortRefTable={}
		for i,v in ipairs(sorted) do
			sortRefTable[tostring(v.id)]=i
		end
		--[[
		if top_id then
			-- swap window (so that top id goes first without rearranging all windows)
			
			local ttid=tostring(top_id)
			local cspot=sortRefTable[ttid]
			if cspot then
				if cspot~=1 then
					sorted[cspot], sorted[1]=sorted[1], sorted[cspot]
				end
				for i,v in ipairs(sorted) do
					sortRefTable[tostring(v.id)]=i
				end
			end
		end
		]]--

		if not table.isEqual(sortRefTable, sortRefTableOld) then
			util.writeFile(fn, "return "..table.tostring(sortRefTable))
		end
		return sorted
	end
end

function map(i,a,b,ta,tb)
	return ((i-a)/(b-a)*(tb-ta)+ta)
end

-- nonuniform divide
function divideHoriz2(out, domain, numWin)
   --print('hehe')
   --print(#out)
   --printTable(out)
   if #out==numWin then return end

   if numWin-#out == 1 then
	  array.pushBack(out, domain)
   else
	   local h=domain.h*split_proportion[configIndex(curr_workspace)]
	  array.pushBack(out, {x=domain.x, y=domain.y, w=domain.w, h=h})
	  if false then -- recursive non-uniform divide
		  divideHoriz2(out, {x=domain.x, y=domain.y+h, w=domain.w, h=domain.h-h},numWin)
	  else -- uniform divide
		  array.concat(out, divideVert({x=domain.x,y=domain.y+h, w=domain.w, h=domain.h-h},numWin-1))
	  end
   end
end

-- uniform divide
function divideHoriz(domain, numWin)

   local out={}
   local gap=horiz_gap
   if numWin==1 then
	  return {deepCopyTable(domain)}
   end
   -- h + gap + h + gap + h = domain.h
   for i=1, numWin do
	  local h=(domain.h-gap*(numWin-1))/numWin
	  array.pushBack(out, 
					 {x=domain.x, 
					  y=map(i,1,numWin, domain.y, domain.y+domain.h-h),
					  w=domain.w,
					  h=h})
   end
	return out
end

function divideVert(domain, numWin)
	local out={}
	local gap=vert_gap
	if numWin==1 then
		return {deepCopyTable(domain)}
	end
	for i=1, numWin do
		local w=(domain.w-gap*(numWin-1))/numWin
		array.pushBack(out, 
		{
			x=map(i,1,numWin, domain.x, domain.x+domain.w-w),
			y=domain.y,
			w=w,
			h=domain.h
		}
		)
	end
	return out
end

function makeSlots(layout, splitProportion, numWin, input)
	
	local lm=monitors[curr_monitor]
	local screen_offset_x=lm.x or screen_offset_x
	local screen_offset_y=lm.y or screen_offset_y
	local screen_width=lm.w or screen_width
	local screen_height=lm.h or screen_height
	screen_offset_x=math.max(screen_offset_x, _G.screen_offset_x)
	screen_offset_y=math.max(screen_offset_y, _G.screen_offset_y)
	screen_width=math.min(screen_width, _G.screen_width)
	screen_height=math.min(screen_height, _G.screen_height)

	if isNatty then -- ubuntu natty behaves differently
		screen_width=screen_width-10
		screen_height=screen_height-20
		-- print(screen_offset_y, screen_height)
	end
	if onboard then
		screen_height=screen_height-onboard.h-20
	end
	
	if numWin==0 then return {} end
	if numWin==1 then
		return {{screen_offset_x,screen_offset_y, screen_width, screen_height}}
	end

	if input then
		local out={}

		for i=1, #input do
			local outi=deepCopyTable(input[i])
			if outi.x<screen_offset_x then
				outi.x=screen_offset_x
			end
			if outi.y<screen_offset_y then
				outi.y=screen_offset_y
			end
			if outi.w>screen_width then outi.w=screen_width end
			if outi.h>screen_height then outi.h=screen_height end

			if outi.x+outi.w>screen_offset_x+screen_width then
				outi.x=screen_width-outi.w+screen_offset_x
			end
			if screen_offset_y+ outi.y+outi.h>screen_offset_y+screen_height then
				outi.y=screen_height-outi.h+screen_offset_y
			end
			out[i]=outi
		end
		return out
	end
	local slots={}
	if layout=="v" then
		local numLeftWin=calcNumLeftWin(numWin)
		--array.pushBack(slots, {x=0,y=0, w=screen_width*splitProportion, h=screen_height})
		array.concat(slots, divideHoriz({x=screen_offset_x,y=screen_offset_y, w=screen_width*splitProportion, h=screen_height}, numLeftWin))

		if false then -- uniform devide
		   array.concat(slots, divideHoriz({x=screen_offset_x+screen_width*splitProportion+vert_gap,y=screen_offset_y, w=screen_width*(1-splitProportion)-vert_gap,h=screen_height},numWin-numLeftWin))
		else
		   local out={}
		   divideHoriz2(out,{x=screen_offset_x+screen_width*splitProportion+vert_gap,y=screen_offset_y, w=screen_width*(1-splitProportion)-vert_gap,h=screen_height},numWin-numLeftWin)
		   array.concat(slots, out)
	   end
   elseif layout=='tv' then
	   array.concat(slots, devideVert({x=screen_offset_x, y=screen_offset_y, w=screen_width, h=screen_height}, numWin))
   elseif layout=='th' then
	   array.concat(slots, devideHoriz({x=screen_offset_x, y=screen_offset_y, w=screen_width, h=screen_height}, numWin))
	elseif layout=='h' then
		local gap=horiz_gap
		local numTopWin=calcNumLeftWin(numWin)
		array.concat(slots, divideVert({x=screen_offset_x,y=screen_offset_y, w=screen_width, h=screen_height*splitProportion},numTopWin))
		array.concat(slots, divideVert({x=screen_offset_x, y=screen_offset_y+screen_height*splitProportion+gap,w=screen_width, h=screen_height*(1-splitProportion)-gap},numWin-numTopWin))
	else -- 't'
		local function split(numRows, numCols,numWin)
			local cols=divideVert({x=screen_offset_x, y=screen_offset_y, w=screen_width, h=screen_height},numCols)
			-- prepare numWinPerColumn
			local numWinPerColumn={}
			local nslot=0
			for i=1,#cols-1 do
				numWinPerColumn[i]=numRows
				nslot=nslot+numRows
			end
			assert(nslot<numWin)
			numWinPerColumn[#cols]=numWin-nslot
			-- adjust num win
			if numWinPerColumn[#cols]>numRows+1 then
				local ccol=#cols-1
				while numWinPerColumn[#cols]>numRows+1 do
					if ccol==0 then break end
					numWinPerColumn[#cols]=numWinPerColumn[#cols]-1
					numWinPerColumn[ccol]=numWinPerColumn[ccol]+1
					ccol=ccol-1
				end
			end
			for i=1, #cols do
				array.concat(slots, divideHoriz(cols[i], numWinPerColumn[i]))
			end
		end
		if screen_width>screen_height then
			if numWin<=3 then
				split(1,2, numWin)
			elseif numWin<6 then
				split(2,2, numWin)
			elseif numWin<9 then
				split(2,3, numWin)
			elseif numWin<12 then
				split(3,3, numWin)
			else
				split(4,3, numWin)
			end
		else
			if numWin<=2 then
				split(2,1, numWin)
			elseif numWin<6 then
				split(2,2, numWin)
			elseif numWin<9 then
				split(3,2, numWin)
			elseif numWin<12 then
				split(3,3, numWin)
			else
				split(3,4, numWin)
			end
		end
	end
	return slots
end

function getWsp(w,px,py) 
	px=px or 0.5
	py=py or 0.5
	return math.floor((w.x+px*w.w)/(screen_offset_x+screen_width))
	+ math.floor((w.y+py*w.h)/(screen_offset_y+screen_height))*numWorkspaceCol
end
function cornerPos(wsp)
	local i=math.mod(wsp-1,numWorkspaceCol)
	local j=math.floor((wsp-1)/numWorkspaceCol)
	return (screen_offset_x+screen_width)*i, (screen_offset_y+screen_height)*j
end

function _rawgetWindowsInCurrWorkspace()
	--local tbl=array.filter(function (w) return w.wsp==0 end, getWindows())
	local tbl=array.filter(
	function (w) -- tile windows that overlap current workspace more than 10%
		if compizEnabled then
			return getWsp(w,0.1)==0 or getWsp(w,0.5)==0 or getWsp(w,0.9)==0 
		else
			return w.wsp==0
		end
	end
	, getWindows())
	for i=1, #tbl do
		local monitor=1
		local w=tbl[i]
		for j=1, #monitors do
			local cx=w.x+w.w/2 
			local cy=w.y+w.h/2 
			local m=monitors[j]
			if cx>=m.x and cy>=m.y and cx<=m.x+m.w and cy<=m.y+m.h then
				monitor=j
				break
			end
		end
		-- print(w.info, monitor)
		w.monitor=monitor
	end
	return tbl
end
function setProperty(win)
	local win_prop=os.capture('xprop -id '..tostring(win.id)..' WM_CLASS WM_STATE _NET_WM_ALLOWED_ACTIONS _NET_WM_STATE _NET_WM_WINDOW_TYPE WM_ICON_NAME')
	--local win_prop=""
	if debug then print(win_prop) end
	local iResizable=string.find(win_prop, "RESIZE")
	win.resizable=iResizable~=nil
	local s,e,cc=string.find(win_prop, "window state: (%a+)")
	win.state=cc
	s,e,cc=string.find(win_prop, 'WM_CLASS%(STRING%) = "([^"]+)')
	win.class=cc
	s,e,cc=string.find(win_prop, 'WM_ICON_NAME%(STRING%) = "([^"]+)')
	win.iconname=cc
	s,e=string.find(win_prop, '_NET_WM_STATE_SHADED')
	if s then win.shaded=true end
	s,e=string.find(win_prop, '_NET_WM_STATE_FULLSCREEN')
	if s then win.fullscreen=true end
	s,e=string.find(win_prop, '_NET_WM_STATE_MAXIMIZED_HORZ')
	if s then win.maximized=true end
	s,e=string.find(win_prop, '_NET_WM_STATE_MAXIMIZED_VERT')
	if s then win.maximized=true end
	s,e,cc=string.find(win_prop, '_NET_WM_WINDOW_TYPE%(ATOM%) = _NET_WM_WINDOW_TYPE_(%a+)')
	if cc=='DOCK' then win.isDock=true end
	if debug then print(s,e,cc) end
end
-- output: { {id=.., x=.., y=.., workspace=.., resizable=.., state=.., class=..}}
function getWindowsInCurrWorkspace(nosort)
	local tbl=_rawgetWindowsInCurrWorkspace()
	-- gather additional information using xprop
	local activeWin=getActiveWindow()
	
	for i,win in ipairs(tbl) do
		if win.id==activeWin then
			win.isActive=true
		end
		setProperty(win)
		if win.iconname=="Onboard" and check_class(win.class,'python') then
			onboard=win
		end
	end
	-- finally check exclude_rule
	local tbl=array.filter( function (w) return not exclude_rule(w) end, tbl)
	if nosort then 
		return sortWindows(nil, tbl)
	else
		return sortWindows(activeWin, tbl)
	end
end
function isVerticallyMaximized(win)
	return win.y-screen_offset_y<SNAP and win.y+win.h>screen_offset_y+screen_height-SNAP 
end
function isHorizontallyMaximized(win)
	return win.x-screen_offset_x<SNAP and win.x+win.w>screen_offset_x+screen_width-SNAP 
end

function calcNumLeftWin(numWin)
	local numLeftWin=1
	if numWin>=5 then
		numLeftWin=2
	end
	return numLeftWin
end
function detectWin1(winInCurrWSP,currMode) -- unsorted list of windows in current workspace
	local pos=array.filter(function (w) return w.x<SNAP and w.y<SNAP end, winInCurrWSP)
	if #pos~=1 then
		return 'failed'
	end
	return pos[1]
end

function moveWindow(id, x,y,w,h)
	if type(x)=='table' then
		y=x.y w=x.w h=x.h x=x.x
	end
	x=math.floor(x+0.5)
	y=math.floor(y+0.5)
	w=math.floor(w+0.5)
	h=math.floor(h+0.5)

	local win={x=x,y=y,w=w,h=h}
	local function exe(cmd)
		if debug then print(cmd) end
		os.execute(cmd)
	end
	if isVerticallyMaximized(win) and isHorizontallyMaximized(win) then
		exe("wmctrl -i -r "..id.." -b remove,fullscreen")
		exe("wmctrl -i -r "..id.." -b add,maximized_vert,maximized_horz")
		return
	end


	--h=h-titlebar_height
	
	exe("wmctrl -i -r "..id.." -b remove,maximized_vert,maximized_horz,fullscreen")
	exe("wmctrl -i -r "..id.." -b remove,maximized_vert,maximized_horz")
	exe("wmctrl -i -r "..id.." -b remove,maximized_vert")
	exe('wmctrl -i -r '..id..' -e 0,'..table.concat({x,y,w,h},","))
	exe('wmctrl -i -r '..id..' -e 0,'..table.concat({x,y,w,h},","))
	if debug then 
		os.execute('wmctrl -lG | grep '..string.sub(string.format('%x', id),-4)) 

		local tbl=getWindowsInCurrWorkspace()
		local pos=array.filter(function (w) return tonumber(w.id)==tonumber(id) end, tbl)[1]
		print(pos.x, pos.y, pos.w, pos.h)
	end

end
function moveWindowAnim(id,x1, y1, w1, h1, x2,y2,w2,h2)
	local anim_step=3
	for i=2, anim_step do
		local x=map(i,1,anim_step, x1, x2)
		local y=map(i,1,anim_step, y1, y2)
		local w=map(i,1,anim_step, w1, w2)
		local h=map(i,1,anim_step, h1, h2)
		moveWindow(id, x,y,w,h)
	end
end
function tileWindow(mode, proportion,tbl)
	if hasFullscreenWin(tbl) then
		for i=1, #tbl do
			if tbl[i].fullscreen then
				os.execute('wmctrl -i -r '..tbl[i].id..' -b remove,fullscreen')
				os.execute('wmctrl -i -r '..tbl[i].id..' -b remove,fullscreen')
				os.execute("wmctrl -i -r "..tbl[i].id.." -b remove,maximized_vert,maximized_horz,fullscreen")
				os.execute("wmctrl -i -r "..tbl[i].id.." -b remove,maximized_vert,maximized_horz")
				os.execute("wmctrl -i -r "..tbl[i].id.." -b remove,maximized_vert")
			end
		end
	end
	local slot
	if mode=='f' or #tbl==1 then
		for i=1, #tbl do
			w=tbl[i]
			if onboard then
				moveWindow(w.id, screen_offset_x,screen_offset_y, screen_width, screen_height-onboard.h-20)
			else
				moveWindow(w.id, screen_offset_x,screen_offset_y, screen_width, screen_height)
			end
		end
	elseif mode=='l' then
		local slot
		if screen_width>screen_height then
			slot= makeSlots('t', 0.5, #tbl)
		else
			slot= makeSlots('t', 0.5, #tbl)
		end
		local tt=math.min(screen_width, screen_height)
		local minWidth=640
		local minHeight=480
		local onboardh=0 if onboard then onboardh=onboard.h end
		if screen_height-onboardh>768 then minHeight=600 end
		if screen_height-onboardh>1000 then minHeight=768 end
		if screen_width>900 then minWidth=800 end
		if screen_width>1100 then minWidth=1024 end
		-- enlarge
		for i=1,#slot do
			if slot[i].w<minWidth then
				slot[i].x=slot[i].x-math.floor((minWidth-slot[i].w)/2)
				slot[i].w=minWidth
			end
			if slot[i].h<minHeight then
				slot[i].y=slot[i].y-math.floor((minHeight-slot[i].h)/2)
				slot[i].h=minHeight
			end
		end
		slot=makeSlots('t',0.5,#tbl, slot) -- clamp
		for i=1, #tbl do
			local w=tbl[i]
			moveWindow(w.id, slot[i].x, slot[i].y, slot[i].w, slot[i].h)
		end
		if onboard then
			local w=onboard
			moveWindow(w.id, screen_width-w.w+screen_offset_x, screen_offset_y+screen_height-w.h, w.w, w.h)
		end
	else
		slot=makeSlots(mode, proportion, #tbl)
		for i=1, #tbl do
			local w=tbl[i]
			moveWindow(w.id, slot[i].x, slot[i].y, slot[i].w, slot[i].h)
		end
	end
	if onboard then
		local w=onboard
		moveWindow(w.id, screen_width-w.w+screen_offset_x, screen_offset_y+screen_height-w.h, w.w, w.h)
	end
	if debug then --debug
		str=""
		for i=1, #tbl do 
			str=str.. tbl[i].info.."\n"
		end
		util.writeFile('/tmp/debug',str)
	end
end
function getTilingMode()
	local cmode=tiling_mode[configIndex(curr_workspace)]
	if _G['tiling_proportion_'..cmode]==nil then
		_G['tiling_proportion_'..cmode]={0.5,0.5,0.5,0.5,0.5,0.5,0.5,0.5}
	end
	local cproportion=_G['tiling_proportion_'..cmode][configIndex(curr_workspace)]
	return cmode, cproportion
end

function retileWindow(tbl)
	local cmode, cproportion=getTilingMode()
	--print(cmode, cproportion)
	--printTable(tiling_mode)
	tileWindow(cmode, cproportion, tbl)
end

function main(arg)

	if arg[1]==nil or arg[1]=='--help' then
		print('p: print info')
		print('w ?: switch to workspace ?')
		print('m ?: move current window to workspace ?')
		print('h : horizontal layout')
		print('v : vertical layout')
		print('l : uniform tile layout')
		print('tile : recent layout')
		print('parsed information:')
		print('\n\n')
		for i=1, #monitors do
			print('monitor ', i, table.tostring(monitors[i]), i==curr_monitor)
		end
		print('screen_offset', screen_offset_x,screen_offset_y)
		print('screen_width,height', screen_width, screen_height)
		print('compizEnabled ', compizEnabled)
		local tbl=getWindows()
		print('id		x	y	w	h	wsp	resizable	class	state info monitor')
		for i=1,#tbl do 
			print(tbl[i].id, tbl[i].x, tbl[i].y, tbl[i].w, tbl[i].h, tbl[i].wsp, tbl[i].resizable, tbl[i].class, tbl[i].state, tbl[i].info, tbl[i].monitor) 
		end
		print('-- current workspace: '..curr_workspace)
		tbl=getWindowsInCurrWorkspace()
		for i=1,#tbl do 
			print(tbl[i].id, tbl[i].x, tbl[i].y, tbl[i].w, tbl[i].h, tbl[i].wsp, tbl[i].monitor, tbl[i].resizable, tbl[i].class, tbl[i].state, tbl[i].info, tbl[i].monitor)  
		end
	elseif arg[1]=='ui' then
		local aw=getActiveWindow()
		local cmode,cproportion=getTilingMode()
		local arg=string.tokenize(os.capture('python ~/.config/mtiler/mstiler_ui.py '..cmode..' '..cproportion),' ')
		printTable(arg)
		for i=1,3 do 
			if getActiveWindow()==aw then break end 
		end
		main(arg)
	elseif arg[1]=='ui2' then
		local aw=getActiveWindow()
		local cmode,cproportion=getTilingMode()
		local cmd='zenity --entry --text "current layout: '..cmode..' '..
		'\n Return : retile'..
		'\n v : vertical layout'..
		'\n q : quit and retile'..
		'"'
		local arg=os.capture(cmd)
		print(arg)
--		for i=1,3 do 
--			if getActiveWindow()==aw then break end 
--		end
		if arg=="" then
			main({'tile'})
		elseif arg=="q" then
			main({'c'})
		elseif tonumber(arg)~=nil then
			main({'w',arg})
		else
			main({arg})
		end
		--main(arg)
	elseif arg[1]=='changeMonitor' then
		local num=tonumber(arg[2])
		if num then
			curr_monitor=clamp(num, 1, #monitors)
			focusWin(1)
			saveConfig()
		end
	elseif arg[1]=='benchmark' then
		local func=_G[arg[2]]
		if func then
			for i=1,100 do
				func()
			end
		else
			print(arg[2], 'not found')
		end
	elseif arg[1]=='wheel' then
		local count=tonumber(arg[2]) or 1
		local h=getActiveWindow()
		if count>0 then
			for i=1,count do
				os.execute('xdotool mousedown --window '..h..' 5')
			end
		else
			for i=1,-1*count do
				os.execute('xdotool mousedown --window '..h..' 4')
			end
		end

	elseif arg[1]=='test' then
		local d_back=debug
		debug=true
		local h=getActiveWindow()
		local tbl=getWindowsInCurrWorkspace()
		local pos=array.filter(function (w) return tonumber(w.id)==tonumber(h) end, tbl)[1]
		moveWindow(h, pos)
		debug=d_back
	elseif arg[1]=='test_movewin' then

		local x=tonumber (arg[3])
		local y=tonumber (arg[4])
		local w=tonumber (arg[5])
		local h=tonumber (arg[6])

		moveWindow(arg[2],x,y,w,h)

	elseif arg[1]=='shade_active' then
		local tbl=getWindowsInCurrWorkspace()
		if tbl[1] then
			if tbl[1].shaded then
				os.execute('wmctrl -i -r '..tbl[1].id..' -b remove,shaded')
			else
				os.execute('wmctrl -i -r '..tbl[1].id..' -b add,shaded')
			end
		end
	elseif arg[1]=='shade_all' then
		local tbl=getWindowsInCurrWorkspace()
		local existShaded=false
		for i,win in ipairs(tbl) do
			if win.shaded then
				existShaded=true
			end
		end
		if existShaded then
			for i,win in ipairs(tbl) do
				if win.shaded then
					os.execute('wmctrl -i -r '..win.id..' -b remove,shaded')
				end
			end
		else
			for i,win in ipairs(tbl) do
				os.execute('wmctrl -i -r '..win.id..' -b add,shaded')
			end
		end

	elseif arg[1]=='w' then
		if compizEnabled then
			local x,y=cornerPos(tonumber(arg[2]))
			os.execute('wmctrl -o '..tostring(x)..','..tostring(y))
		else
			os.execute('wmctrl -s '..tostring(arg[2]-1))
		end
	elseif arg[1]=='m' then
		--local currWorkspace=
		--os.execute('wmctrl -o '..tostring(tonumber(arg[2]-1)*1280)..',0')
	elseif arg[1]=='n' then -- focus next
		focusWin(1)
	elseif arg[1]=='p' then -- focust prev
		focusWin(-1)
	elseif arg[1]=='focusWin' then
		local cmode,cproportion=getTilingMode()
		local tbl=getWindowsInCurrWorkspace(true)
		local win1=detectWin1(tbl, cmode)
		if win1~="failed" then
			local tbl=sortWindows(win1.id, tbl)
			local num=tonumber(arg[2])
			if tbl[num] then
				os.execute('wmctrl -i -R '..tbl[num].id)
			end
		end
	elseif arg[1]=='launch-gui-app' then
		local tbl=getWindowsInCurrWorkspace(true)
		local h=getActiveWindow()
		local app=table.concat(arg, '" "', 2)
		app='"'..app..'"'
		if string.sub(app,-2)=='&"' then
			app=string.sub(app,1, -3)..'"'
		end
		print(app)
		if true then -- polling for a new window
			os.execute(app.."&")
			local tbl2=getWindowsInCurrWorkspace(true)
			for i=1,80 do -- polling
				if(#tbl~=#tbl2) then break end
				if debug then print('polling') end
				if i>60 then
					os.sleep(1) -- slow down polling speed
				end
				tbl2=getWindowsInCurrWorkspace(true)
			end
			if #tbl~=#tbl2 then
				retileWindow(tbl2)
				os.sleep(1)
				retileWindow(tbl2)
			else
				print("Time expired in launching "..app)
			end
		else
			os.execute('xtoolwait '..app)
			local tbl2=getWindowsInCurrWorkspace(true)
			retileWindow(tbl2)
		end
	elseif arg[1]=='term' then
		main({'launch-gui-app', 'gnome-terminal'})
		--main({'launch-gui-app', 'roxterm'})
	elseif arg[1]=='luashell' then -- my own lua shell prompt
		--local ttt=os.capture('wish ~/.config/mtiler/gui-prompt "lua-shell"')
		local ttt=os.capture('python ~/.config/mtiler/mstiler_prompt.py "lua-shell"')
		--local ttt=os.capture('zenity --entry --text "lua-shell"')
		dofile(script_path.."/commands.lua")
		if ttt~="" then
			if shortcuts[ttt] then
				local sc=shortcuts[ttt]
				notify('launching "'..table.concat(shortcuts[ttt],'" "')..'"...')
				if sc.working_folder then
					local prefix='\nlua ~/.config/mtiler/mstiler.lua launch-gui-app "'
					if sc.noRetile then
						prefix='\n"'
					end
					util.writeFile(script_path.."/_temp", 'cd '..sc.working_folder..prefix..table.concat(shortcuts[ttt], '" "')..'"')
					os.execute("sh "..script_path.."/_temp")
				elseif sc.noRetile then
					os.execute('"'..table.concat(sc, '" "')..'"')
				else
					main({'launch-gui-app', unpack(shortcuts[ttt])})
				end
			elseif scripts[ttt] then
				scripts[ttt]()
			else
				if os.isFileExist('/usr/local/bin/l') then
					os.execute('l '..ttt)
				else
					notify('launching '..ttt)
					main({'launch-gui-app', ttt})
				end
			end
		end
	elseif arg[1]=='tile' then
		local tbl=getWindowsInCurrWorkspace()
		retileWindow(tbl)
	elseif arg[1]=='shift' then -- shift workspace
		local h=getActiveWindow()
		-- print(string.format('%x',h))
		local wsp=tonumber(arg[2])
		if wsp~=curr_workspace then
			local tbl=getWindowsInCurrWorkspace()
			local w=array.filter(function (w) return tonumber(w.id)==tonumber(h) end, tbl)[1]
			if compizEnabled then
				local cx,cy=cornerPos(curr_workspace)
				local nx,ny=cornerPos(wsp)
				moveWindow(w.id, w.x+nx-cx,w.y+ny-cy, w.w, w.h)
			else
				os.execute('wmctrl -i -r '..w.id..' -t '..tostring(wsp-1))
			end
			exclude_id={h} -- move window might take time.
			main({'tile'})
			exclude_id={}
		end
	elseif arg[1]=='vf' then --vertical fill
		local tbl=getWindowsInCurrWorkspace()
		for i=1, #tbl do
			local w=tbl[i]
			moveWindow(w.id, w.x,screen_offset_x, w.w, screen_height)
		end
	elseif arg[1]=='hf' then --horizontal fill
		local tbl=getWindowsInCurrWorkspace()
		for i=1, #tbl do
			local w=tbl[i]
			moveWindow(w.id, screen_offset_y,w.y, screen_width, w.h)
		end
	elseif arg[1]=='c' then --close
		local wtc=getActiveWindow()
		exclude_id={wtc}
		main({'tile'}) -- retile
		exclude_id={}
		os.execute('wmctrl -i -c '..wtc)
	elseif arg[1]=="c+" or arg[1]=="c-" then
		arg[1]=string.sub(arg[1],2)
		local h=getActiveWindow()
		local tbl=getWindowsInCurrWorkspace()
		local pos=array.filter(function (w) return tonumber(w.id)==tonumber(h) end, tbl)[1]
		if pos==nil then print('?') return end
		local amt=screen_width/8
		if arg[1]=="-" then amt=amt*-1 end
		local function clampDir(i, amt, a,b)
			if i<a+SNAP or i>b-SNAP then
				if debug then print('snap', i, amt, a, b) end
				--return i
				if i<a+SNAP then return a end
				if i>b-SNAP then return b end
			end
			if amt>0 then
				if i+amt>b-SNAP-3 then
					return b-SNAP-3
				else
					return i+amt
				end
			else
				if i+amt<a+SNAP+3 then
					return a+SNAP+3
				else
					return i+amt
				end
			end
		end
		if debug then print('x') end
		local x=clampDir(pos.x,-amt,screen_offset_x,screen_width) 
		if debug then print('y') end
		local y=clampDir(pos.y,-amt,screen_offset_y,screen_height)
		if debug then print('w') end
		local w=clampDir(pos.x+pos.w,amt,x,screen_offset_x+screen_width)-x
		if debug then print('h') end
		local h=clampDir(pos.y+pos.h,amt,y,screen_offset_y+screen_height)-y

		if debug then print(x,y,w,h, pos.x, pos.y, pos.w, pos.h, amt) end
		moveWindow(pos.id,x,y,w,h)
		--moveWindowAnim(pos.id,pos.x, pos.y, pos.w, pos.h, x,y,w,h)
	elseif arg[1]=='F' then -- fullscreen
		local tbl=getWindowsInCurrWorkspace()
		local fullscreen=hasFullscreenWin(tbl)
		if fullscreen then
			for i=1, #tbl do
				os.execute('wmctrl -i -r '..tbl[i].id..' -b remove,fullscreen')
			end
		else
			if fullscreen_only_current_window then
				os.execute('wmctrl -i -r '..tbl[1].id..' -b add,fullscreen')
			else
			-- make all windows full-screen
				for j=1,#tbl do
					local i=#tbl-j+1
					os.execute('wmctrl -i -r '..tbl[i].id..' -b add,fullscreen')
				end
				--os.execute('wmctrl -i  -R '..tbl[1].id)
			end
		end

	elseif arg[1]=="v+" or arg[1]=="v-" then
		local cmode,cproportion=getTilingMode()
		if cmode=='v' then
			local adjust_step=-0.1
			if arg[1]=="v+" then adjust_step=0.1 end
			split_proportion[configIndex(curr_workspace)]=clamp(split_proportion[configIndex(curr_workspace)]+adjust_step, 0.5, 0.9)
			saveConfig()
			local tbl=getWindowsInCurrWorkspace(true)
			tileWindow(cmode, cproportion, tbl)
		else
			main({'c'..string.sub(arg[1],2)})
		end

	elseif arg[1]=="adjustProportion" or arg[1]=="+" or arg[1]=="-" or arg[1]=="invertProportion" then

		local cmode,cproportion=getTilingMode()
		local tbl=getWindowsInCurrWorkspace(true)
		local win1=detectWin1(tbl, cmode)
		local tbl=sortWindows(win1.id, tbl)
		local adjust_step=adjust_step[cmode]
		if adjust_step then
			if arg[1]=="-" then
				cproportion=clamp(cproportion-adjust_step, adjust_step, 1-adjust_step)
			elseif arg[1]=="+" then
				cproportion=clamp(cproportion+adjust_step, adjust_step, 1-adjust_step)
			elseif arg[1]=="adjustProportion" then
				cproportion=tonumber(arg[2])
			else
				cproportion=1-cproportion
			end
			--print(cproportion)
			tileWindow(cmode, cproportion, tbl)
			_G['tiling_proportion_'..cmode][configIndex(curr_workspace)]=cproportion
			saveConfig()
		end
	elseif arg[1]=='moveUp' or arg[1]=='moveDown' then
		local cmode,cproportion=getTilingMode()
		local tbl=getWindowsInCurrWorkspace(true)
		local up=1.5
		if arg[1]=='moveDown' then up=-1.5 end
		local tbl=sortWindows(getActiveWindow(), tbl,up)
		tileWindow(cmode, cproportion, tbl)
	elseif arg[1]=='f' then
		changeMode('f')
	elseif arg[1]=='cycleMode' then
		local cmode,cproportion=getTilingMode()
		cmode=cycle_mode_table[cmode]
		if cmode=='t' and #getWindowsInCurrWorkspace(true)<=3 then 
			notify_prefix=notify_prefix..'(skipping '..modes[cmode]..') '
			cmode=cycle_mode_table[cmode]
		end
		changeMode(cmode)
	elseif arg[1]=='changeMode' then
		cmode=arg[2]
		changeMode(cmode)
	elseif arg[1]=='rotate' then

		if true then -- a trick to avoid an onboard bug
			local tbl=getWindowsInCurrWorkspace(true) -- check if onboard window is visible
			if onboard then
				local w=onboard
				local screen_width=math.min(screen_width, screen_height)
				local screen_height=math.min(screen_width, screen_height)
				local offset=0
				moveWindow(w.id, screen_width-w.w, screen_height-w.h, w.w, w.h)
			end
		end
		local fn=script_path..'/mtiler_xrandr.rc'
		local o=util.readFile(fn)
		local curRot='normal'
		if o then
			curRot=string.lines(o)[1]
		end
		local rotMap={normal='right', right='left', inverted='normal', left='normal'}

		--local rotMap={normal='right', right='inverted', inverted='left', left='normal'}
		local wacomMap={normal='NONE', right='CCW', left='CW',inverted='half' }
		local newRot=rotMap[curRot]

		os.execute('xrandr -o '..newRot)
		os.execute('xsetwacom set "Serial Wacom Tablet stylus" Rotate '..wacomMap[newRot])
		os.execute('xsetwacom set "Serial Wacom Tablet eraser" Rotate '..wacomMap[newRot])

		if newRot=='left' then
			util.writeFile('/dev/shm/temp2.sh',
			string.gsub([[xmodmap - <<END
			NEWLINEkeycode 113 = Up NoSymbol Up NoSymbol Up
			NEWLINEkeycode 116 = Left NoSymbol Left NoSymbol Left
			NEWLINEkeycode 111 = Right NoSymbol Right NoSymbol Right
			NEWLINEkeycode 114 = Down NoSymbol Down NoSymbol Down
		NEWLINEEND
		]],"[ \t]*NEWLINE",""))
		os.execute('sh /dev/shm/temp2.sh')
		elseif newRot=='right' then
			util.writeFile('/dev/shm/temp2.sh',
			string.gsub([[xmodmap - <<END
			NEWLINEkeycode 114 = Up NoSymbol Up NoSymbol Up
			NEWLINEkeycode 111 = Left NoSymbol Left NoSymbol Left
			NEWLINEkeycode 116 = Right NoSymbol Right NoSymbol Right
			NEWLINEkeycode 113 = Down NoSymbol Down NoSymbol Down
		NEWLINEEND
		]],"[ \t]*NEWLINE",""))
		os.execute('sh /dev/shm/temp2.sh')
		elseif newRot=='normal' then
			util.writeFile('/dev/shm/temp2.sh',
			string.gsub([[xmodmap - <<END
			NEWLINEkeycode 111 = Up NoSymbol Up NoSymbol Up
			NEWLINEkeycode 113 = Left NoSymbol Left NoSymbol Left
			NEWLINEkeycode 114 = Right NoSymbol Right NoSymbol Right
			NEWLINEkeycode 116 = Down NoSymbol Down NoSymbol Down
		NEWLINEEND
		]],"[ \t]*NEWLINE", ""))
		os.execute('sh /dev/shm/temp2.sh')
		end
		util.writeFile(fn, newRot)
		os.execute('rm '..script_path..'/mtiler.rc') -- invalidate current resolution cache.
		os.execute('lua ~/.config/mtiler/mstiler.lua changeMode l')
	end
end

function table.isEqual(tbl1, tbl2)
	assert(tbl1~=nil)
	assert(tbl2~=nil)

	for k,v in pairs(tbl1) do
		if type(v)=="table" then
			if type(tbl2[k])~="table" then
				return false
			elseif not isEqual(tbl2[k],v) then
				return false
			end
		else
			if tbl2[k]~=v then
				return false
			end
		end
	end

	for k,v in pairs(tbl2) do
		if tbl1[k]==nil then
			return false
		end
	end

	return true
end

if arg then
	if arg[1]=='--debug' then
		debug=true
		arg[1]=arg[2]
		arg[2]=arg[3]
		arg[3]=arg[4]
	end
	main(arg)
end
